package com.io.nio.groupchat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class ChatServer {

    private static Selector selector;
    private static ServerSocketChannel serverSocketChannel;
    private static final int PORT=6379;

    public ChatServer() {
        try {
            selector=Selector.open();//得到选择器

            serverSocketChannel= ServerSocketChannel.open();//得到通道

            //设置通道为非阻塞，不设置程序好像会出现问题
            serverSocketChannel.configureBlocking(false);

            //监听端口
            serverSocketChannel.socket().bind(new InetSocketAddress(PORT));

            //给ServerSocketChannel通道注册监听事件为连接事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void listen() {

        //循环监听，selector由Nio的线程来维护
        while (true){
            try {
                //等待一秒看看是否有发生事件的通道，如果不设置的话，就会一直阻塞等待，也有唤醒的方法
                //int count = selector.select(1 * 1000);

                //为了测试，直接让这个线程阻塞在这监听（实际生产中是不可能这样的）
                // 不然刚启动服务器，还没来得及启动客户端，由于1秒之内没有连接，服务器直接停掉了
                int count = selector.select();
                if (count>0){
                    System.out.println("有"+count+"个通道发生了事件");

                    //获取所有事件发生的SelectionKey的set集合
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();

                    //得到set集合的迭代器
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();

                    //遍历迭代器，并进行相关的业务操作
                    while (iterator.hasNext()){
                        SelectionKey selectionKey = iterator.next();
                        //判断是否为连接事件
                        if (selectionKey.isAcceptable()){
                            SocketChannel accept = serverSocketChannel.accept();
                            //设置为非阻塞
                            accept.configureBlocking(false);
                            System.out.println("ip为"+accept.getRemoteAddress().toString().
                                    substring(1)+"的用户上线了");
                            accept.register(selector,SelectionKey.OP_READ);
                        }
                        if (selectionKey.isReadable()){
                            //专门写一个方法处理这个业务
                            readData(selectionKey);
                        }
                        //删除当前的SelectionKey
                        iterator.remove();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
            }
        }
    }

    //处理可读状态下的通道的业务逻辑
    private static void readData(SelectionKey selectionKey) {
        SocketChannel socketChannel=null;
        try {
            socketChannel = (SocketChannel)selectionKey.channel();
            //设置为非阻塞
            //socketChannel.configureBlocking(false);
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int read = socketChannel.read(buffer);
            if (read>0){
                //将缓冲区中的数据转成字节数组并输出（数据比较大的时候应该使用while循环遍历输出）
                String msg = new String(buffer.array());
                System.out.println(msg);

                // 注意：转发的是除自己以外的其他客户端
                //然后将消息转发给其他客户端，将自己的socketChannel传过去便于把自己的管道排除
                sendOtherClient(msg,socketChannel);
            }

        }catch (Exception e){//如果处理线异常说明用户离线或者是其他特殊情况
            try {
                System.out.println("用户"+socketChannel.getRemoteAddress()+"，离线了");
                //取消注册的selectionKey
                selectionKey.cancel();
                //关闭通道
                socketChannel.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    private static void sendOtherClient(String msg, SocketChannel selfSocketChannel) {
        System.out.println("服务器转发消息中...");
        //获取所有注册事件的SelectionKey,下面注释掉的代码不知道与那个没注释的有啥区别，一直错，醉了
//        Set<SelectionKey> keys = selector.keys();
//        Iterator<SelectionKey> iterator = keys.iterator();
//        while (iterator.hasNext()){
//            SelectionKey selectionKey = iterator.next();
//            SocketChannel targetChannel = (SocketChannel)selectionKey.channel();
//            //当目标targetChannel不是自己时进行相应的业务处理
//           if (targetChannel!=selfSocketChannel){
//               //将数据写入缓冲区，使用wrap方法可以分配msg大小的字节数据，更好的利用了空间，避免空间浪费
//               ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
//               try {//将缓冲区中的数据写入管道
//                   targetChannel.write(buffer);
//               } catch (IOException e) {
//                   e.printStackTrace();
//               }
//               iterator.remove();
//           }
//        }
        for(SelectionKey key: selector.keys()) {

            //通过 key  取出对应的 SocketChannel
            Channel targetChannel = key.channel();

            //排除自己
            if(targetChannel instanceof  SocketChannel && targetChannel != selfSocketChannel) {

                //转型
                SocketChannel dest = (SocketChannel)targetChannel;
                //将msg 存储到buffer
                ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                //将buffer 的数据写入 通道
                try {
                    dest.write(buffer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        ChatServer chatServer = new ChatServer();
        chatServer.listen();
    }

}
